<?php
/**
 * @license http://www.gnu.org/licenses/lgpl.html LGPL
 * @author Harlan Iverson <h dot iverson at gmail dot com>
 * @copyright Copyright (c) 2006, Harlan Iverson.
 * @version $Id: FacesContext.class.php 22 2006-08-04 05:02:56Z Harlan $
 */



require_once( "PhpFaces/Application/Application.php" );
require_once( "PhpFaces/Context/ExternalContext.php" );
require_once( "PhpFaces/Application/ViewHandler.php" );
require_once( "PhpFaces/Application/NavigationHandler.php" );
require_once( "PhpFaces/EL/EL.php" );

class PhpFaces_Context_FacesContext {
	
	const SCOPE_REQUEST = "request";
	const SCOPE_SESSION = "session";
	const SCOPE_PAGE = "page";
	const REQUEST_OBJECT_SEPERATOR = "__";
	const REQUEST_PREFIX = "faces-";
	const ACTION_PREFIX = "action-";
	const EL_VARIABLE_PATTERN = "/(\#\{([^\}]+)\})/";
	
	private static $currentInstance = null;
	
	private $navigationRules;
	private  $beanContext;
	

	private $application;
	private $logger;
	private $viewRoot;
	private $externalContext;

	/**
	 * Setup the context
	 */
	public function __construct() {
			$this->beanContext = array();
			$this->navigationRules = array();
			
			$this->elContext = new PhpFaces_EL_ELContext();
			$newScope = array( "test" => "test value" );
			$this->elContext->putContext( self::SCOPE_REQUEST, $newScope );
			
			$this->logger = LoggerManager::GetLogger( "PhpFaces.FacesContext" );
			$this->logger->debug( "Created FacesContext $this" );
	}
	
	/**
	 * Get the current instance
	 * @deprecated 8 - Jul 30, 2006 Use getCurrentInstance instead
	 */
	public static function &currentInstance() {
		if( self::$currentInstance == null ) {
			self::setCurrentInstance( new PhpFaces_Context_FacesContext() );	
		}
		return self::$currentInstance;	
	}
	
	/**
	 * Get the current FacesContext instance
	 */
	public static function &getCurrentInstance() {
		return self::currentInstance();
	}
	
	/**
	 * Set the current FacesContext instance
	 */
	public static function setCurrentInstance( &$instance ) {
		self::$currentInstance = $instance;
		$instance->logger->debug( "setCurrentInstance $instance" );
	}

	public function getApplication() {
		if( $this->application == null ) {
			$application = new PhpFaces_Application_Application();
			$this->application = $application;
		}
		return $this->application;
	}
	
	private $responseWriter;
	public function getResponseWriter() {
		return $this->responseWriter;
	}
	public function setResponseWriter( XmlWriter $writer ) {
		$this->responseWriter = $writer;
	}

	public function getResponseStream() {
		return $this->getExternalContext()->getOutputStream();
	}
	
	public function getExternalContext() {
		if( $this->externalContext == null ) {
			$this->externalContext = new PhpFaces_Context_ExternalContext();
		}
		return $this->externalContext;
	}
	
	private $renderKitId;
	public function getRenderKitId() {
		return $this->renderKitId;
	}
	
	public function setRenderKitId( $id ) {
		$this->renderKitId = $id;
	}
	
	private $elContext;
	public function getELContext() {
		return $this->elContext;
	}
	
	/**
	 * Resolve an EL expression to local context...
	 * This will soon be succeeded by a VariableResolver class
	 * @param $el string the EL expression to look up
	 * @deprecated The new VariableResolver needs to be written, but this will stick around for a bit.
	 */
	public function &resolve( $el ) {
		if( preg_match( self::EL_VARIABLE_PATTERN, $el ) ) {
			$elFragment = el_to_path( $el );
			
			$resolver = $this->getApplication()->getVariableResolver();
			$varRef = $resolver->resolveVariable( $this, $elFragment );
			
			return $varRef;
		} else {
			return $el;
		}
	}
	
	
	/**
	 * Resolve a variable from a "context," which is currently just an associatave array. It will
	 * take an EL path and traverse objects and assoc arrays recusively until it finds
	 * the expression you were looking for. It will throw a PhpFaces_EL_VariableNotFoundException if
	 * it does not find the desired path. 
	 * @param $elFragment EL expression relative to $context
	 * @param &$context the context to resolve the next level of $beanPath in
	 * @return a reference to the requested variable; a callable reference if it was a method.
	 */
	protected function &resolveFromContext( $elFragment, &$context ) {
		if( $context == null || (is_array( $context ) && count( $context ) == 0 ) ) {
			throw new PhpFaces_EL_VariableNotFoundException( "Empty context trying to resolve $elFragment" );
		}
		
		$varParts = split( "\.", $elFragment );
		
		$varRef = $context;
		
		for( $i = 0; $i < count($varParts); $i++ ) {
			$partName = $varParts[$i];  
			if( is_array( $varRef ) && array_key_exists( $partName, $varRef ) ) {
				$varRef =& $varRef[$partName];
			} else if( is_object( $varRef ) && property_exists( $varRef, $partName ) ) {
				$varRef =& $varRef->$partName;
			} else if( is_object( $varRef ) && method_exists( $varRef, "get".$partName ) ) {
				$accessor = "get".$partName;
				$varRef =& $varRef->$accessor();
			} else if( is_object( $varRef ) && method_exists( $varRef, $partName ) ) {	
				$actionRef = array( &$varRef, $partName);
				$varRef =& $actionRef;;
			} else {
				throw new PhpFaces_EL_VariableNotFoundException( $elFragment );
			}
		}
		
		return $varRef;;
	}

	
	/**
	 * Runs the first (and should be only) action found and 
	 * returns out outcome string.
	 * @param &$fromContext mixed the context from which to extract the action name. generally $_POST.
	 * @return string the outcome, as returned by the callable action
	 */
	public function runAction( &$fromContext ) {
		$actionRef =& $this->getAction( $fromContext );
		
		if( $actionRef != null ) {	
			//echo "run action: "; print_r($actionRef);
			
			$outcome = call_user_func( $actionRef );
			
		return $outcome;
		}

	}
	/**
	 * Lookup the current action that should be executed from a given context
	 * @param &$fromContext mixed the context from which to extract the action name. generally $_POST.
	 * @return mixed a callable reference to the action to execute
	 */
	public function &getAction( &$fromContext ) {

		$action = $this->getCurrentActionName( $fromContext );
		$action = str_replace( self::REQUEST_OBJECT_SEPERATOR, ".", $action );

		if( $action ) {
			$action = '#{'.$action.'}';
		}
		$actionRef =& $this->resolve( $action ); // ...

		return $actionRef ;
	}
	
	
	/**
	 * Works the same as getRequestName, but instread resolves the name of an action.
	 * @see getRequestName
	 */
	public function getActionName( $el ) {
		$var = el_to_path( $el );
		return self::ACTION_PREFIX.str_replace( ".", self::REQUEST_OBJECT_SEPERATOR, $var );
	}
	
	
	/**
	 * Look up the name (EL) of the current action
	 * @param &$fromContext mixed the context from which to extract the action name. generally $_POST.
	 * @todo handle _x/_y suffic of image buttons as submitted by IE
	 */
	public function getCurrentActionName( &$fromContext ) {
		foreach( $fromContext as $k => $v ) {
			if( substr( $k, 0, strlen(self::ACTION_PREFIX) ) == self::ACTION_PREFIX 
				&& substr( $k, -2 ) != "_x" /* */
				&& substr( $k, -2 ) != "_y") {
					
				$action = substr( $k, strlen(self::ACTION_PREFIX) );
				$action = str_replace( self::REQUEST_OBJECT_SEPERATOR, ".", $action );
				return $action; //
			}
		}
	}

	/**
	 * Returns a POST variable name to be used on forms for certain expressions.
	 * The way this is done is nothing like JSF, and should perhaps be rethought.
	 * The reason for deviation is that PhpFaces simply does not have a StateHolder,
	 * or unique clientIds for each component. 
	 * @var $el string the EL expression to turn into a variable name
	 */
	public function getRequestName( $el ) {
		$el = el_to_path( $el );
		return self::REQUEST_PREFIX.str_replace( ".", self::REQUEST_OBJECT_SEPERATOR, $el );
	}
	
	/**
	 * Get the value of a request variable. I don't think this is used anymore...
	 * @todo see if this is used anymore
	 * @param $el string the EL expression to look up
	 */
	public function getRequestValue( $el ) {
		list( $beanName, $rest ) = split( "\.", $el, 2 );
		$rest = str_replace(".", "->", $rest);;
		return '{$'.$beanName.'->'.$rest.'}';;
	}
	
	
	
	/**
	 * Register a bean into the bean context
	 */
	public function registerBean( $name, $objectClass, $scope ) {

		$this->logger->debug( "Registered Bean: $scope.$name, class=$objectClass" );

		$object = null;

		if( $scope == self::SCOPE_SESSION ) {
			if( isset( $_SESSION[ self::SCOPE_SESSION ] )
				&& isset( $_SESSION[ self::SCOPE_SESSION ][$name] ) ) {
				$object =& $_SESSION[ self::SCOPE_SESSION ][$name];
			} else {
				$object = new $objectClass;
				$_SESSION[ self::SCOPE_SESSION ][$name] =& $object ;
			}	
		} else { // request scope
			$object = new $objectClass;
		}

		$elContext =& $this->getELContext()->getContext( $scope );
		$elContext[ $name ] =& $object;
		
		$this->beanContext[ $name ] =& $object;
	}
	
	/**
	 * Get an assoc aray of all registered beans
	 * @deprecated  use getResolverContext instead
	 */
	public function &getRegisteredBeans() {
		return $this->getResolverContext();	
	}
	
	public function &getResolverContext() {
		return $this->beanContext;	
	}
	
	/**
	 * Register an array of outcome handlers
	 * @todo rename this / get rid of it
	 * @params &$navigationRules PhpFaces_Config_NavigationRuleConfigEntry[] an array of navigation rule entries
	 */
	public function registerNavigationRules( &$navigationRules ) {
		for( $i = 0; $i < count( $navigationRules ); $i++ ) {
			$this->registerNavigationRule( $navigationRules[ $i ] );
		}
	}
	
	/**
	 * Register a single outcome handler
	 * @todo rename this / get rid of it
	 * @param PhpFaces_Config_NavigationRuleConfigEntry a single rule
	 */
	public function registerNavigationRule( &$navigationRule ) {
		$this->navigationRules[] = $navigationRule;
	}
	
	/**
	 * Get an array of outcome handlers
	 * @todo rename this / get rid of it
	 * @return PhpFaces_Config_NavigationRuleConfigEntry[] 
	 */
	public function getNavigationRules() {
		return $this->navigationRules;
	}


	/**
	 * I don't think this is even used anymore, superceded by LifecycleImpl
	 * The "Assign Values" phase of JSF... Takes request variables, as mapped by 
	 * {@see getRequestName} and {@see getActionName}, and assigns them to actual 
	 * beans
	 * @param &$fromContext mixed the context from which to extract these values. generally $_POST.
	 * @return boolean true if there were no validation errors, false otherwise
	 * @todo Implement FacesMessage stuff
	 */
	public function assignValues( &$fromContext ) {

		$this->logger->warn( "assignValues? I thought this wasn't used anymore" );

		$isValid = false;

		/* while */ foreach( $fromContext as $k => $v ) {
			if( substr( $k, 0, strlen(PhpFaces_Context_FacesContext::REQUEST_PREFIX) ) == self::REQUEST_PREFIX ) {

				$varName = substr( $k, strlen(self::REQUEST_PREFIX) );
				$varName = str_replace( self::REQUEST_OBJECT_SEPERATOR, ".", $varName );

				$requiredSuffix = "-required";
				$isRequired = false;

				if( substr( $varName, -1*strlen($requiredSuffix) ) == $requiredSuffix ) {
					$varName = substr( $varName, 0, -1*strlen( $requiredSuffix ) );
					$isRequired = true;
				}
				$isEmpty = (!$v || !trim($v));

				if( $isRequired && !$isEmpty ) {
					$isValid = true;
					continue;
				}
				if( $varName ) {
					$varName = '#{'.$varName.'}';
				}
				$varRef =& $this->resolve( $varName );

				$varRef = $v;
			}
		}

		$success = !$isValid;
		return $success;		
	}
	
	public function getViewRoot() {
		// TODO do we return a default view?
		return $this->viewRoot;
	}
	public function setViewRoot( $viewRoot ) {
		$this->viewRoot = $viewRoot;
	}
}
?>
